home *** CD-ROM | disk | FTP | other *** search
/ Magnum One / Magnum One (Mid-American Digital) (Disc Manufacturing).iso / d18 / kruse_11.arc / $NDEXTEX.PAS next >
Pascal/Delphi Source File  |  1990-11-30  |  31KB  |  906 lines

  1.  
  2. {
  3.  1. Program IndexText(InText, InIndex, NewIndex, OutIndex,
  4.                               HashFile, Input, Output);
  5.  2.    Function Lt(u, v: word):  Boolean;
  6.  3.    Procedure ReadWord(var F: text;  var w: word);
  7.  4.    Procedure WriteWord(var F: text; w: word);
  8.  
  9.  5.    Procedure SplitWords;                                     phase 1
  10.  6.      Function HashAddress(w: word):  hashentry;
  11.  7.      Procedure Initialize;
  12.  8.      Procedure GetWord;
  13.  9.        Procedure GetChar(var ch: char);
  14. 10.        Procedure AddChar(ch: char);
  15. 11.      Procedure Conclude;
  16.  
  17. 12.    Procedure ClassifyWords;                                  phase 2
  18. 13.      Procedure BuildTree(var root: pointer; ch: char);
  19. 14.        Procedure Insert(p: pointer);
  20. 15.        Function Power2(c: integer): level;
  21. 16.        Procedure FindRoot;
  22. 17.        Procedure ConnectSubtrees;
  23. 18.        Procedure GetNode(var p: pointer; ch: char);
  24. 19.      Procedure Process(r: reference);
  25. 20.        Procedure UpdateNode(p: pointer; r: reference);
  26. 21.        Procedure NewWord(var p: pointer; r: reference);
  27. 22.        Procedure InsertTree(r, p: pointer);
  28. 23.      Procedure OutputTree(p: pointer);
  29. 24.        Procedure PutNode(p: pointer);
  30.  
  31. 25.    Procedure UpdateHashFile;                                 phase 3
  32. 26.      Function HashAddress(w: word): hashentry;
  33. 27.    Procedure MergeIndices;
  34. 28.      Procedure CopyLine
  35. }
  36.  
  37.  
  38. Program IndexText(InText, InIndex, NewIndex, OutIndex, HashFile,
  39.                   NewHashFile, Input, Output);
  40. {Produces word counts and list of references for the document file InText.
  41.  Uses the master word list in file InIndex, if provided.
  42.  Output word list for the new text goes to file NewIndex.
  43.  The merger of these two files becomes OutIndex.
  44.  HashFile  contains the common words to be ignored.  If not specified, it is
  45.  created on output, containing the words so flagged by the user.}
  46. Const
  47.   maxwd         =   20;    {More letters in a word will be ignored.}
  48.   minwd         =    3;    {Shorter words will be ignored}
  49.   hashsize      = 2003;    {should be a prime;  size of hash table}
  50.   linesperpage  =   66;    {assumes standard spacing and paper}
  51.   maxheight     =   20;    {for building binary tree in phase 2}
  52.   A = 'A';
  53.   Z = 'Z';
  54.   hyphen        =  '-';
  55.   blank         =  ' ';
  56.   apostrophe    = '''';    {requires two apostrophes to represent one}
  57.   underscore    =  '_';
  58.   ordbackspace  =    8;    {ASCII control character for backspace}
  59.   ordformfeed   =   12;    {ASCII control character for new page}
  60.   changecase    =   32;    {ASCII difference between upper and lower case}
  61. Type
  62.   word          =  packed array[1..maxwd] of char;
  63.   reference     =  record
  64.                      wd:   word;
  65.                      pg:   integer;  {page number}
  66.                  end;
  67.   fileref       =  file of reference;  {used for local files}
  68.   letter        =  A..Z;
  69.   hashentry     =  1..hashsize;
  70. Var
  71.   InText,                       {document being processed}
  72.   InIndex,                      {master word list}
  73.   NewIndex,                     {word list of current document}
  74.   OutIndex:     text;           {updated master word list}
  75.   HashFile, 
  76.   NewHashFile:  file of word;   {local file, used to update HashFile}
  77.   RefFile:      array[letter] of fileref;     {local files used for
  78.                      auxiliary storage of words from phase 1 to phase 2:
  79.                      separate file for each initial letter}
  80.   blankword:    word;           {will contain all blanks}
  81.   outcount:    array[letter] of integer;  {counters for word files}
  82.   wordcount:   integer;         {count of all words in the text}
  83.  
  84.  
  85.  
  86. Function Lt(u,v:  word):   Boolean;
  87. {Determine if word u precedes word v lexicographically.}
  88. Var
  89.   i:    1..maxwd;               {loop variable}
  90. Begin                           {function Lt}
  91.   i := 1;
  92.   While (i < maxwd) and (u[i] = v[i]) do   i := i + 1;
  93.   Lt := (u[i] < v[i])
  94.   {Above is version that works with ASCII code. For codes where blank comes 
  95.                 after letters, modifications are necessary.}
  96. End;                            {function Lt}
  97.  
  98.  
  99. Procedure ReadWord( var F: text;  var w: word);
  100. {reads word w from text file F; assumes not at end of file}
  101. Var
  102.   c:  1..maxwd;
  103. Begin                           {procedure ReadWord}
  104.   For c := 1 to maxwd do
  105.     read(F, w[c])
  106. End;                            {procedure ReadWord}
  107.  
  108.  
  109.  
  110. procedure WriteWord(var F: text; w: word);
  111. {writes word w to text file F}
  112. var
  113.   c:  1..maxwd;
  114. begin
  115.   for c := 1 to maxwd do
  116.     write(F, w[c])
  117. end;
  118.  
  119.  
  120.  
  121.  
  122.  
  123. {Phase 1:  Splitting the Text into Words}
  124.  
  125.  
  126. Procedure SplitWords;
  127. {sets up hash table, reads text, and divides into 26 word lists}
  128. Var
  129.   hash:        array[hashentry] of word;     {hash table}
  130.   pagecount,                    {keeps the current page number}
  131.   addpage,                      {amount to increase pagecount after word}
  132.   linecount:   integer;         {line number on the current page}
  133.   w:           word;            {word currently being processed}
  134.   x:           hashentry;       {location of w, if in hash table}
  135.   endinput:    Boolean;         {true if and only if input has all been read}
  136.   firstletter: char;            {Into which file does word w go?}
  137. {The following are kept for use in procedure GetWord,
  138.  and for efficiency are set up only once in procedure Initialize.}
  139.   backspace,
  140.   formfeed:    char;            {ASCII control characters}
  141.   contchar,                     {characters OK in the middle of a word}
  142.   alphabet:    set of char;     {letters only --- to start a word}
  143. {Implementation dependent: A good Pascal compiler should allow "set of char";
  144.  otherwise, a restricted range is required.}
  145.  
  146.  
  147.  
  148.   function HashAddress(w: word): hashentry;   {modified from the textbook}
  149.   {calculates the location in hash table of word w, or, if not there,
  150.    returns pointing to the blank word where w should go}
  151.  
  152.   var
  153.     x,                            {calculated location}
  154.     inc:     integer;             {increment for open addressing}
  155.   begin                           {function HashAddress}
  156.     x := abs(ord(w[1])*ord(w[2])+ord(w[4])+ord(w[6])) mod hashsize + 1;
  157. {Hash function assumes long word length. For short word machines
  158.  we must ensure that the result is non-negative, and worry about overflow.}
  159.  
  160.     if (hash[x] <> w) and (hash[x] <> blankword) then
  161.       begin
  162.         inc   := (abs(ord(w[3])-95) mod 29);
  163.                   {A key dependent increment is used to avoid clustering.}
  164.         repeat
  165.           inc := inc + 1;
  166.           if inc > hashsize then
  167.             writeln(w,' causes hash table to become full, infinite loop.');
  168.           x := x + inc;
  169.           if x > hashsize then x := x - hashsize;
  170.         until (w =  hash[x])  or  (blankword = hash[x])
  171.       end;
  172.     HashAddress := x
  173.   end;                            {function HashAddress}
  174.  
  175.  
  176. Procedure Initialize;
  177. {sets up constant-valued sets for use in GetWord;
  178.  opens the text file  and initializes various counters;
  179.  opens file holding hash table (if any), and reads or
  180.  otherwise initializes the table.}
  181. Var
  182.   ch:         char;             {used as an index}
  183.   i:          integer;          {general--purpose loop control}
  184. Begin                           {procedure Initialize}
  185.   backspace:= chr(ordbackspace);
  186.   formfeed := chr(ordformfeed); {Initialize ASCII control characters.}
  187.   alphabet := ['A'..'Z', 'a'..'z'];      {letters only, to start a word}
  188.   contchar := alphabet + [hyphen, apostrophe, backspace, underscore];
  189.                         {characters that will not terminate the word}
  190.   For i := 1 to maxwd do
  191.     blankword[i] := blank;
  192.   reset(InText);
  193.   endinput := eof(InText);
  194.   Repeat
  195.     write( 'What is the page number on which the text begins?');
  196.     readln(pagecount);
  197.     if pagecount < 0 then
  198.       writeln('Must be a nonnegative integer.')
  199.   until pagecount >= 0;
  200.   linecount := 0;
  201.   addpage   := 0;
  202.   wordcount := 0;
  203.   For ch := A to Z do
  204.   Begin
  205.     Rewrite( RefFile[ch] );
  206.     Outcount[ch] := 0
  207.   End;
  208.   reset(HashFile);
  209.   if eof(HashFile) then
  210.   begin     {There is no previous table; initialize the table to all blanks.}
  211.     writeln('Cannot open file for hash table. Creating a new table.');
  212.     for i := 1 to hashsize do
  213.       hash[i] := blankword
  214.   end 
  215.   else begin                {Retrieve the previous hash table.}
  216.     i := 0;
  217.     repeat
  218.       i := i + 1;
  219.       hash[i] := HashFile^;
  220.       get(HashFile)
  221.     until eof(HashFile) or (i >= hashsize);
  222.     if (not eof(HashFile)) or (i <> hashsize) then
  223.       writeln('Error in reading hash table. Incorrect number of entries.')
  224.   end
  225. end;                            {procedure Initialize}
  226.  
  227.  
  228.  
  229. Procedure GetWord( var w: word);
  230. {Gets words from input file InText, and returns only words
  231.  at least minwd characters long.  Parameter endinput becomes
  232.  true if and only if the end of InText is reached with no word to return.
  233.  This parameter is set by the subsidiary procedure GetChar.
  234.  The procedure also updates global variables wordcount and linecount,
  235.  updates the global variable pagecount after each linesperpage cr's,
  236.  or after each formfeed, whichever comes first, and
  237.  uses the sets alphabet and contchar and various character constants.}
  238. label 1;           {used by GetChar to exit procedure on eof(InText)}
  239. Var  c:      0..maxwd;          {count of characters in word}
  240.      ch:     char;              {character currently processed}
  241.      endln:  Boolean;           {At the end of a line?}
  242.  
  243.  
  244. Procedure GetChar(var ch: char);
  245. {gets a character from input text into ch; checks for eof;
  246.  updates page count and line count}
  247. Begin                           {procedure GetChar}
  248.   If eof(InText) then
  249.     If c >= minwd then
  250.       ch := '.'                 {special character to end the current word}
  251.     Else begin                  {no word to return; set endinput}
  252.       endinput := true;
  253.       goto 1                    {Exit from GetWord.}
  254.     End
  255.   Else begin                    {not at end of file: process next character}
  256.     ch := InText^;
  257.     endln := eoln(InText);
  258.     get(InText);
  259.     If endln then
  260.     Begin
  261.       linecount := linecount + 1;
  262.       If linecount >= linesperpage then
  263.         Begin
  264.           addpage := addpage + 1;
  265.           linecount := 0
  266.         End
  267.     End;
  268.     If ch = formfeed then
  269.     Begin
  270.       addpage := addpage + 1;
  271.       linecount := 0;
  272.       endln := true;        {Treat formfeed like end of line.}
  273.       ch := blank
  274.     End
  275.   End
  276. End;        {procedure GetChar}
  277.  
  278.  
  279.  
  280. Procedure AddChar(ch: char);
  281. {adds given character to word, if possible}
  282. Begin                           {procedure AddChar}
  283.   If c < maxwd then
  284.   Begin
  285.     c := c + 1;
  286.     w[c] := ch
  287.   End
  288. End;                            {procedure AddChar}
  289.  
  290.  
  291.  
  292. begin                           {procedure GetWord}
  293.   repeat                   {until current word is at least minwd chars long}
  294.     c := 0;
  295.     repeat
  296.       GetChar(ch)               {Find a letter that will start the word.}
  297.     until ch in alphabet;
  298.     pagecount := pagecount + addpage;
  299.     addpage := 0;
  300.     If ch in ['a'..'z'] then    {Translate the first letter to uppercase.}
  301.       ch := chr(ord(ch) - changecase);      {system dependent}
  302.     AddChar(ch);                {Put first letter into the word.}
  303.     GetChar(ch);
  304.     While ch in contchar do
  305.       If ch in alphabet then    {Add letters directly to word.}
  306.       Begin                     {processing letter}
  307.         AddChar(ch);
  308.         GetChar(ch)
  309.       End                       {processing letter}
  310.       Else If ch = hyphen then
  311.       Begin                     {processing hyphen}
  312.         GetChar(ch);            {Find what comes after hyphen.}
  313.         If endln then
  314.           GetChar(ch)           {Delete both the hyphen and the end of line.}
  315.         Else if ch = hyphen then    {Two hyphens represent a dash.}
  316.           ch := blank           {Use a blank to terminate the word.}
  317.         Else If ch in alphabet then
  318.           AddChar(hyphen)       {Include hyphens between letters}
  319.         Else    {nothing}       {Delete all other hyphens}
  320.       End                       {processing hyphen}
  321.       Else if ch = apostrophe then
  322.       Begin                     {processing apostrophe}
  323.         GetChar(ch);
  324.         If ch = 's' then        {Delete 's at end of word only.}
  325.         Begin
  326.           GetChar(ch);
  327.           If ch in contchar then
  328.           Begin
  329.             AddChar(apostrophe);
  330.             AddChar('s')
  331.           End
  332.         End
  333.         Else if ch in alphabet then
  334.            AddChar(apostrophe)  {Allow contractions.}
  335.       End                       {processing apostrophe}
  336.       Else         {Remaining possibilities are backspace and underscore.}
  337.         GetChar(ch);            {Delete these characters.}
  338.               {while loop on continuing characters ends here.}
  339.     wordcount := wordcount + 1
  340.   Until c >= minwd;             {Skip over short words.}
  341.   While c < maxwd do            {Fill with blanks.}
  342.   Begin
  343.     c := c + 1;
  344.     w[c] := blank
  345.   End;
  346. 1:                 {When end of file occurs, program will exit to here from GetChar.}
  347. End;                            {procedure GetWord}
  348.  
  349.  
  350.  
  351. Procedure Conclude;
  352. {Writes out counts of various word lists. For some systems, it is 
  353.    necessary to close files, which should be done in this procedure.}
  354. Var
  355.   ch:      char;                {loop index}
  356. Begin                           {procedure Conclude}
  357.   writeln('The total number of words read in is ', wordcount:7);
  358.   writeln;
  359.   writeln('The number of words to process further in the next stage,');
  360.   writeln('beginning with each letter, is below.');
  361.   writeln;
  362.   for ch := 'A' to 'M' do write(' ', ch:1, ' ');
  363.   writeln;
  364.   for ch := 'A' to 'M' do write(outcount[ch]:4, ' ');
  365.   writeln;
  366.   writeln;
  367.   for ch := 'N' to 'Z' do write(' ', ch:1, ' ');
  368.   writeln;
  369.   for ch := 'N' to 'Z' do write(outcount[ch]:4, ' ');
  370.   writeln;
  371.   writeln
  372. End;                            {procedure Conclude}
  373.  
  374.  
  375. Begin                           {procedure SplitWords}
  376.   Initialize;                   {sets up files, hash table, constants}
  377.   GetWord(w);                   {obtains a single word from InText}
  378.   While not endinput do
  379.   Begin
  380.     x := HashAddress(w);
  381.     If w <> hash[x] then
  382.     Begin                       {Not in hash table; put into RefFile.}
  383.       firstletter := w[1];
  384.       outcount[firstletter] := outcount[firstletter] + 1;
  385.       With RefFile[firstletter]^ do
  386.       Begin
  387.         wd := w;
  388.         pg := pagecount
  389.       End;
  390.       Put(RefFile[firstletter])
  391.     End;
  392.     GetWord(w)
  393.   End;
  394.   Conclude                      {writes word counts to Output}
  395. End;                            {procedure SplitWords}
  396.  
  397.  
  398.  
  399.  
  400.  
  401. {Phase 2:  Classifying the words}
  402.  
  403.  
  404. Procedure ClassifyWords;
  405. {For each letter of the alphabet, the procedure reads in a list of words from
  406.  InIndex, builds them into a binary tree, supplements it with entries from
  407.  RefFile, and writes result to NewIndex and NewHashFile.}
  408. Type
  409.   wordtype  = (hash, count, index);     {three ways to process a word}
  410.   pointref  = ^reflist;
  411.   reflist   = record                    {list of references}
  412.                 pg:   integer;
  413.                 next: pointref
  414.               end;
  415.   pointer   = ^node;
  416.   node      = record                    {vertex of the binary tree}
  417.                 wd:       word;
  418.                 left,
  419.                 right:    pointer;
  420.               case kind:  wordtype of
  421.                 hash:
  422.                   ();     {empty}
  423.                 count:
  424.                   (ct:    integer);
  425.                 index:
  426.                   (ref:   pointref)
  427.               end;
  428. Var
  429.   root:       pointer;          {root of the binary tree}
  430.   ch:         char;             {Loop on the first letter of word.}
  431.  
  432.  
  433.  
  434. procedure BuildTree(var root: pointer; ch: char);
  435. {Uses an auxiliary procedure GetNode(p) to obtain a list of items in
  436.  proper order of keys, and builds them into a binary search tree.}
  437. const  
  438.   maxheight = 20;
  439. type
  440.   level = -1 .. maxheight;   {number of steps above leaves}
  441. var
  442.   lastnode:  array[level] of pointer;   {contains a pointer to
  443.                        the last node processed on each level}
  444.   counter:   integer;    {number of nodes read in so far}
  445.   p:         pointer;    {p^ is the present input node}
  446.   lev:       level;      {level of p^}
  447.  
  448.  
  449.   function Power2(c:  integer): level;
  450.   {Finds the highest power of 2 that divides c.  Requires c <> 0.}
  451.   var
  452.     lev:   level;
  453.   begin                        {Function Power2}
  454.     lev := 0;
  455.     while not odd(c) do
  456.       begin  c := c div 2;  lev := lev + 1  end;
  457.     Power2 := lev
  458.   end;                         {Function Power2}
  459.  
  460.  
  461.   procedure Insert(p: pointer);
  462.   {Inserts p^ as rightmost node of a partial binary search tree.}
  463.   var
  464.     lev:       level;      {level of p^}
  465.   begin                    {Procedure Insert}
  466.     lev      := Power2(counter);
  467.     p^.right := nil;
  468.     p^.left  := lastnode[lev - 1];
  469.     lastnode[lev] := p;
  470.     if lastnode[lev + 1] <> nil then
  471.       with lastnode[lev + 1]^ do
  472.       if right = nil then right := p
  473.   end;                     {Procedure Insert}
  474.  
  475.  
  476.   procedure FindRoot;
  477.   var
  478.     lev:    level;
  479.   begin                    {Procedure FindRoot}
  480.     if counter = 0 then
  481.       root := nil          {Tree is empty.}
  482.     else begin             {Non-empty tree}
  483.       lev := maxheight;    {Find the highest occupied level; it gives the root}
  484.       while lastnode[lev] = nil do lev := lev - 1;
  485.       root := lastnode[lev]
  486.     end
  487.   end;                     {Procedure FindRoot}
  488.  
  489.  
  490.   procedure ConnectSubtrees;
  491.   var
  492.     p:         pointer;
  493.     lev:       level;
  494.     s:         level;
  495.   begin                    {Procedure ConnectSubtrees}
  496.     lev := maxheight;
  497.     while (lastnode[lev] = nil) and (lev > 1) do
  498.       lev := lev - 1;      {Find the highest node:  root}
  499.     while lev > 1 do       {Nodes on levels 1 and 0 are already OK}
  500.       with lastnode[lev]^ do
  501.       if right <> nil then
  502.         lev := lev - 1     {Search down for the highest dangling node}
  503.       else begin           {Case:  right subtree is undefined.}
  504.         p := left;         {Find the highest entry in lastnode that}
  505.         s := lev - 1;                     {is not in the left subtree.}
  506.         repeat
  507.           p := p^.right;
  508.           s := s - 1
  509.         until (p = nil) or (p <> lastnode[s]);
  510.         right := lastnode[s];
  511.         lev := s           {Nodes on levels between lev and s are on the left.}
  512.       end                  {Connecting dangling subtrees}
  513.   end;                     {Procedure  ConnectSubtrees}
  514.  
  515.  
  516.  
  517. Procedure GetNode( var p: pointer;  ch: char);
  518. {reads a word from file  InIndex  and sets node correspondingly;
  519. returns p = nil at eof or when next word starts later than ch}
  520. Var
  521.   wordcode:  char;              {letter indicating type of word}
  522. Begin                           {procedure GetNode}
  523.   While (not eof(InIndex)) and (InIndex^ = blank) do
  524.     Get(InIndex);               {Skip all the leading blanks.}
  525.   If eof(InIndex) then
  526.     p := nil
  527.   Else if InIndex^ > ch then
  528.     p := nil
  529.   Else begin
  530.     new(p);
  531.     with p^ do begin
  532.       ReadWord(InIndex, wd);
  533.       Read(InIndex, wordcode);
  534.       If wordcode = 'i'
  535.         then begin kind := index;  ref := nil  end
  536.       Else if wordcode = 'c'
  537.         then begin kind := count;  ct  := 0    end
  538.       Else
  539.         Writeln('Erroneous word code in file InIndex.')
  540.     End;                        {with statement setting up node}
  541.     readln(InIndex)             {Advance to the start of the next entry.}
  542.   End
  543. End;                            {procedure GetNode}
  544.  
  545.  
  546.  
  547. begin                                             {Procedure BuildTree}
  548.   for lev := -1 to maxheight do  lastnode[lev] := nil;
  549.   counter := 0;
  550.   GetNode(p, ch);
  551.   while p <> nil do
  552.   begin
  553.     counter  := counter + 1;
  554.     Insert(p);
  555.     GetNode(p, ch)
  556.   end;                   {receiving and processing input}
  557.   FindRoot;
  558.   ConnectSubtrees
  559. end;                                             {Procedure  BuildTree}
  560.  
  561.  
  562.  
  563.  
  564. Procedure Process( r: reference);
  565. {takes the word and page reference r and updates the binary tree}
  566. Var
  567.   p:          pointer;          {Trace through the tree.}
  568.   found:      Boolean;          {Is the word in the tree?}
  569.  
  570.  
  571.  
  572. Procedure UpdateNode( p:  pointer;  r: reference);
  573. {uses reference r to update information in node p^}
  574. Var
  575.   q:     pointref;              {used to add reference to list}
  576. Begin                {procedure UpdateNode}
  577.   With p^ do
  578.     Case  kind  of
  579.       hash:;                    {no action needed}
  580.       count: ct := ct + 1;
  581.       index: If ref = nil then
  582.              Begin
  583.                new(ref);
  584.                ref^.pg   := r.pg;
  585.                ref^.next := nil
  586.              End
  587.              Else if ref^.pg <> r.pg then
  588.              Begin            {Add the new reference to the list.}
  589.                New(q);
  590.                q^.pg   := r.pg;
  591.                q^.next := ref;
  592.                ref     := q
  593.              End
  594.     End                         {case statement to update tree}
  595. End;                            {procedure UpdateNode}
  596.  
  597.  
  598.  
  599. Procedure NewWord(var p: pointer;  r: reference);
  600. {Creates a node for the first occurrence of a new reference r.
  601.  A pointer to the new node is returned in p.}
  602. Var
  603.   response:       char;         {answer received from user}
  604. Begin                           {procedure NewWord}
  605.   new(p);
  606.   With p^ do
  607.   Begin
  608.     wd    := r.wd;
  609.     left  := nil;
  610.     right := nil;
  611.     Repeat                      {Ask user what kind of word.}
  612.       WriteWord(output, wd);
  613.       write('is (F, C, I)?');
  614.       read(response)
  615.     Until response in ['F', 'C', 'I' ,'f', 'c', 'i'];
  616.     Case response of
  617.        'F','f': kind := hash;
  618.        'C','c': Begin
  619.                   kind := count;
  620.                   ct   := 1
  621.                 End;
  622.        'I','i': Begin
  623.                   kind := index;
  624.                   new(ref);
  625.                   ref^.pg   := r.pg;
  626.                   ref^.next := nil;
  627.                 End
  628.     End                         {case statement}
  629.   End                           {with statement}
  630. End;                            {procedure NewWord}
  631.  
  632.  
  633.  
  634. Procedure InsertTree(r, p: pointer);
  635. {adds a node p^ to the tree with root r^;  requires that r <> nil
  636.  and p^ not be in the tree; proceeds by recursion}
  637. Begin                           {procedure InsertTree}
  638.   If Lt(p^.wd, r^.wd) then
  639.     If r^.left = nil then r^.left := p
  640.     Else InsertTree(r^.left, p)
  641.   Else
  642.     If r^.right = nil then r^.right := p
  643.     Else InsertTree(r^.right, p)
  644. End;                            {procedure InsertTree}
  645.  
  646.  
  647.  
  648.  
  649. Begin                           {procedure Process}
  650.   If root = nil then            {The tree might be empty.}
  651.     NewWord(root, r)
  652.   Else begin                    {case of nonempty tree}
  653.     p := root;                  {Begin a tree search.}
  654.     found := false;
  655.     Repeat
  656.       If r.wd = p^.wd then
  657.         found := true
  658.       Else If Lt(r.wd, p^.wd) then
  659.         p := p^.left
  660.       Else
  661.         p := p^.right
  662.     Until found or (p = nil);
  663.     If found then UpdateNode(p, r)
  664.     Else begin                  {p^ was not found:  add it to the tree.}
  665.       NewWord(p, r);
  666.       InsertTree(root, p)
  667.     End
  668.   End
  669. End;                            {procedure Process}
  670.  
  671.  
  672.  
  673.  
  674. Procedure OutputTree( p: pointer);
  675. {traverses the tree for which p^ is the root in inorder}
  676.  
  677.  
  678. Procedure PutNode(p:  pointer);
  679. Var
  680.   q:        pointref;           {used to traverse list of references}
  681. Begin                           {procedure PutNode}
  682.   With p^ do
  683.     Case  kind  of
  684.       hash: Begin
  685.               NewHashFile^ := wd;
  686.               put( NewHashFile )
  687.             End;
  688.       count: If ct <> 0 then   {Otherwise, word is not in the document.}
  689.              Begin
  690.                WriteWord(NewIndex, wd);
  691.                write(NewIndex, 'c');
  692.                writeln( NewIndex, ct:5)
  693.              End;
  694.       index: If ref <> nil then
  695.              Begin
  696.                WriteWord(NewIndex, wd);
  697.                write(NewIndex, 'i');
  698.                q := ref;
  699.                Repeat
  700.                  write( NewIndex, q^.pg:5);
  701.                  q := q^.next
  702.                Until q = nil;
  703.                writeln( NewIndex )
  704.              End
  705.     End                         {case statement}
  706. End;                            {procedure PutNode}
  707.  
  708.  
  709. Begin                           {procedure OutputTree}
  710.   If p <> nil then
  711.   With p^ do
  712.   Begin
  713.     OutputTree(left);           {Traverse the left subtree.}
  714.     PutNode(p);
  715.     OutputTree(right);          {Traverse the right subtree.}
  716.     Dispose(p)
  717.   End
  718. End;                            {procedure OutputTree}
  719.  
  720.  
  721.  
  722.  
  723. Begin                           {procedure ClassifyWords}
  724.   writeln('At the appearance of each word, give its disposition:');
  725.   writeln(' F --- Forget all occurrences of this word.');
  726.   writeln(' C --- Count how many times this word appears.');
  727.   writeln(' I --- Index this word: list the pages on which it appears.');
  728.   Reset(InIndex);
  729.   Rewrite(NewIndex);
  730.   For ch := A to Z do           {Start main loop on first letter of word.}
  731.   Begin
  732.     BuildTree(root, ch);  {Get the part of master wordlist starting with ch
  733.                     from the file InIndex, and build it into a binary tree.}
  734.     reset(RefFile[ch]);
  735.     While not eof(RefFile[ch]) do
  736.     Begin
  737.       Process(RefFile[ch]^); 
  738.           {Use new words from RefFile[ch] to update the tree.}
  739.       get( RefFile[ch] )
  740.     End;
  741.     OutputTree(root)   {Write the contents of the tree into files NewIndex and 
  742.                                 NewHashFile.}
  743.   End                           {main loop on letters of alphabet}
  744. End;                            {procedure ClassifyWords}
  745.  
  746.  
  747.  
  748.  
  749.  
  750. {Phase 3:  Updating the Permanent Files}
  751.  
  752.  
  753. Procedure UpdateHashFile;
  754. {reads in old hash table, inserts file of new entries; writes out to HashFile}
  755. Var
  756.   hash:   array[hashentry] of word;
  757.   x:      hashentry;
  758.   w:      word;
  759.  
  760.  
  761. Function HashAddress(w: word): hashentry;
  762. {calculates the location in hash table of word w, or, if none,
  763.  returns pointing to the blank word where w should go}
  764. Var
  765.   x,                         {calculated location}
  766.   inc: integer;          {increment for open addressing}
  767. Begin                        {function HashAddress}
  768.   x := (ord(w[1]) * ord(w[3]) * ord(w[4]) + ord(w[6])) mod hashsize + 1;
  769.   {Hash function assumes long word length. For short word machines, we
  770.    must ensure that the result is nonnegative, and worry about overflow.}
  771.   If (hash[x] <> w) and (hash[x] <> blankword) then
  772.   Begin
  773.     inc   := 1;
  774.     Repeat
  775.       x := x + inc;
  776.       If x > hashsize then x := x - hashsize;
  777.       inc := inc + 2
  778.     Until (w = hash[x]) or (blankword = hash[x])
  779.   End;
  780.   HashAddress := x
  781. End;                          {function HashAddress}
  782.  
  783.  
  784. Begin                           {procedure UpdateHashFile}
  785.   reset(HashFile);
  786.   If eof(HashFile) then         {HashFile is empty; create new table.}
  787.     For x := 1 to hashsize do
  788.       hash[x] := blankword
  789.   Else
  790.     For  x := 1 to hashsize do
  791.       read(HashFile, hash[x]);
  792. {Some versions of Pascal do not allow procedures read and write for
  793.      files other than text.  For such systems, expand to use get and put.}
  794.   reset(NewHashFile);
  795.   While not eof(NewHashFile) do
  796.   Begin
  797.     read(NewHashFile, w);
  798.     hash[HashAddress(w)] := w
  799.             {If the table is full, new entries will replace old ones.}
  800.   End;
  801.   rewrite(HashFile);
  802.   For x := 1 to hashsize do
  803.     Write(HashFile, hash[x])
  804. End;                            {procedure UpdateHashFile}
  805.  
  806.  
  807.  
  808.  
  809. Procedure MergeIndices;
  810. {merges files NewIndex and InIndex into file OutIndex}
  811. Var
  812.   u, v:   word;        {for new and old indices, respectively}
  813.   m, n:   integer;    {counts for above entries}
  814.   ukind,
  815.   vkind:  char;                 {Is the word of kind i or c?}
  816.  
  817.  
  818. Procedure CopyLine( var w: word; var F: text; newline, endline:  Boolean);
  819. {Copies the remainder of a line from the file F to OutIndex.
  820.  If newline is true, then the word w is also written, and kind is copied.
  821.  If endline is true, then the line written to OutIndex is ended.
  822.  The procedure also reads a new word w from the next line in F.}
  823. Var
  824.   n:      integer;              {number copied from file to file}
  825.   kind:   char;                 {word code copied from file to file}
  826. Begin                           {procedure CopyLine}
  827.   If newline then
  828.   Begin
  829.     WriteWord(OutIndex, w);
  830.     read(F, kind);
  831.     write(OutIndex, kind)
  832.   End Else
  833.     While (not eof(F)) and (not eoln(F)) and (F^ = blank) do
  834.       get(F);
  835.   While (not eof(F)) and (not eoln(F)) do
  836.   Begin
  837.     read(F, n);
  838.     write(OutIndex,  n:5);
  839.     While (not eoln(F)) and (F^ = blank) do
  840.       get(F);                   {Skip blanks.}
  841.   End;
  842.   readln(F);
  843.   If not eof(F) then
  844.     ReadWord(F, w);
  845.   If endline then writeln(OutIndex)
  846. End;                            {procedure CopyLine}
  847.  
  848.  
  849.  
  850. Begin                           {procedure MergeIndices}
  851.   reset(NewIndex);
  852.   reset(InIndex);
  853.   rewrite(OutIndex);
  854.   If eof(NewIndex) or eof(InIndex) then
  855.     writeln('One of the indices is empty.  No merge will be done.')
  856.   Else Begin
  857.     ReadWord(NewIndex, u);
  858.     ReadWord( InIndex, v);
  859.     Repeat
  860.       If Lt(u,v) then
  861.         CopyLine(u, NewIndex, true, true)
  862. {Boolean parameters mean, respectively;  start new line; end the line.}
  863.       Else If Lt(v,u) then
  864.         CopyLine(v,  InIndex, true, true)
  865.       Else begin                {Words are equal.  Determine the kind of word.}
  866.         read(NewIndex, ukind);
  867.         read( InIndex, vkind);
  868.         If ukind <> vkind then
  869.           writeln('Inconsistent word types found in merge.');
  870.         WriteWord(OutIndex, u);
  871.         write(OutIndex, ukind);
  872.         If ukind = 'c' then
  873.         Begin
  874.           readln(NewIndex, m);
  875.           readln( InIndex, n);
  876.           m := m + n;
  877.           writeln(OutIndex, m:5);
  878.           If not eof(NewIndex) then ReadWord(NewIndex, u);
  879.           If not eof( InIndex) then ReadWord( InIndex, v)
  880.         End
  881.         Else begin              {Copy both lists of page numbers.}
  882.           CopyLine(u, NewIndex, false, false);
  883.           CopyLine(v,  InIndex, false, true)
  884.         End
  885.       End        {finished processing equal words}
  886.     Until eof(NewIndex) or eof(InIndex);
  887.     While not eof(NewIndex) do
  888.       CopyLine(u, NewIndex, true, true);
  889.     While not eof(InIndex) do
  890.       CopyLine(v, InIndex, true, true)
  891. {At most one of the two loops above will iterate.}
  892.   End
  893. End;                            {procedure MergeIndices}
  894.  
  895.  
  896.  
  897.  
  898.  
  899. Begin                           {main program}
  900.   SplitWords;                   {phase 1}
  901.   ClassifyWords;                {phase 2}
  902.   UpdateHashFile;               {phase 3, first part}
  903.   MergeIndices;                 {phase 3, second part}
  904. End.
  905.  
  906.